﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using TribesPSO;

namespace RosenbrockBenchmark
{
    class Program
    {
        /// <summary>
        /// This is a toy program that shows how to do a partical swarm optimization of a common benchmark.  This program also takes the
        /// number of loops to run as an argument.  If no loop count is specified, 500 loops will be performed
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            int loops;
            if (args.Length == 1) //one argument provided
            {
                if (int.TryParse(args[0], out loops))
                {
                    //Cool, we parsed argument 0
                }
                else
                {
                    loops = 500;
                }
            }
            else //No arguments provided or 2+ arguments provided.  Don't try to figure it out, just do 500 loops
            {
                loops = 500;
            }

            Console.WriteLine("********** Hypersphere Particles **********");
            string normalResults = Run(loops);
            Console.WriteLine(normalResults);
            Console.WriteLine();

            Console.WriteLine("********** Independent Gaussian Particles**********");
            string gaussianResults = RunIndependantGaussian(loops);
            Console.WriteLine(gaussianResults);

            Console.ReadLine();
        }

        /// <summary>
        /// Does the actual optimization of the rosenbrock function using a single threaded search space that contains particles that move
        /// via hyperspheres
        /// </summary>
        /// <param name="loops"> The number of optimizations to perform</param>
        /// <returns>A string containing information about the optimizations</returns>
        static string Run(int loops)
        {
            List<int> EvalCount = new List<int>();
            List<TimeSpan> Runtimes = new List<TimeSpan>();
            //Do the actual calculations
            for (int n = 0; n < loops; n++)
            {
                Rosenbrock objectiveFunction = new Rosenbrock(); //See Rosenbrock.cs to see how to make your own function to minimize
                var SearchSpace = new SingleThreadedHypersphereSearchSpace(objectiveFunction,new HyperspaceRandom(n));

                DateTime start = DateTime.Now; //Keep track of when we start so we know how long this takes

                for (int moves = 0; moves < 5000; moves++) //5000 is a "magic number"  I've never seen a rosenbrock benchmark take longer
                {
                    SearchSpace.MoveThenAdapt(); //Do the operation
                    if (SearchSpace.BestSolution.Error < .001) break; //If we've found a good enough solution, take the early out
                }

                DateTime finish = DateTime.Now;

                EvalCount.Add(objectiveFunction.Evaluations);
                Runtimes.Add(new TimeSpan(finish.Ticks - start.Ticks));
            }

            //Put together the result string
            StringBuilder resultBuilder = new StringBuilder();
            resultBuilder.AppendLine("********** Average Evaluations **********");
            resultBuilder.AppendLine(EvalCount.Average().ToString());
            resultBuilder.AppendLine("********** Time **********");
            resultBuilder.AppendLine("Average Time: " + new TimeSpan((long)Runtimes.Average(o => o.Ticks)).ToString());
            resultBuilder.AppendLine("Total Time: " + new TimeSpan(Runtimes.Sum(o => o.Ticks)).ToString());

            var TicksPerEval = EvalCount.Zip(Runtimes, (eval, time) => (double)time.Ticks / (double)eval);
            resultBuilder.AppendLine("Average Ticks per Evaluation: " + TicksPerEval.Average());

            return resultBuilder.ToString();
        }

        /// <summary>
        /// This method does the same optimization as Run(int loops) but uses particles that move via independant gaussian functions
        /// </summary>
        /// <param name="loops">The number of optimizations to perform</param>
        /// <returns>A string containing information regarding the optimizations</returns>
        static string RunIndependantGaussian(int loops)
        {
            //See the comments in "Run" for information on what's going on here.  It's nearly identical except for the type of search space we're using
            List<int> EvalCount = new List<int>();
            List<TimeSpan> Runtimes = new List<TimeSpan>();
            for (int n = 0; n < loops; n++)
            {
                Rosenbrock objectiveFunction = new Rosenbrock();
                var SearchSpace = new SingleThreadedGaussianSearchSpace(objectiveFunction,new HyperspaceRandom(n));
                DateTime start = DateTime.Now;
                for (int moves = 0; moves < 5000; moves++)
                {
                    SearchSpace.MoveThenAdapt();
                    if (SearchSpace.BestSolution.Error < .001) break;
                }
                DateTime finish = DateTime.Now;

                EvalCount.Add(objectiveFunction.Evaluations);
                Runtimes.Add(new TimeSpan(finish.Ticks - start.Ticks));
            }

            StringBuilder resultBuilder = new StringBuilder();
            resultBuilder.AppendLine("********** Average Evaluations **********");
            resultBuilder.AppendLine(EvalCount.Average().ToString());
            resultBuilder.AppendLine("********** Time **********");
            resultBuilder.AppendLine("Average Time: " + new TimeSpan((long)Runtimes.Average(o => o.Ticks)).ToString());
            resultBuilder.AppendLine("Total Time: " + new TimeSpan(Runtimes.Sum(o => o.Ticks)).ToString());

            var TicksPerEval = EvalCount.Zip(Runtimes, (eval, time) => (double)time.Ticks / (double)eval);
            resultBuilder.AppendLine("Average Ticks per Evaluation: " + TicksPerEval.Average());

            return resultBuilder.ToString();
        }
    }
}
